/*
*
* GSM 07.10 Implementation with User Space Serial Ports
*
* Copyright (C) 2003  Tuukka Karvonen <tkarvone@iki.fi>
*
* Version 1.0 October 2003
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* Modified November 2004 by David Jander <david@protonic.nl>
*  - Hacked to use Pseudo-TTY's instead of the (obsolete?) USSP driver.
*  - Fixed some bugs which prevented it from working with Sony-Ericsson modems
*  - Seriously broke hardware handshaking.
*  - Changed commandline interface to use getopts:
*
* Modified January 2006 by Tuukka Karvonen <tkarvone@iki.fi> and 
* Antti Haapakoski <antti.haapakoski@iki.fi>
*  - Applied patches received from Ivan S. Dubrov
*  - Disabled possible CRLF -> LFLF conversions in serial port initialization
*  - Added minicom like serial port option setting if baud rate is configured.
*    This was needed to get the options right on some platforms and to 
*    wake up some modems.
*  - Added possibility to pass PIN code for modem in initialization
*   (Sometimes WebBox modems seem to hang if PIN is given on a virtual channel)
*  - Removed old code that was commented out
*  - Added support for Unix98 scheme pseudo terminals (/dev/ptmx)
*    and creation of symlinks for slave devices
*  - Corrected logging of slave port names
*  - at_command looks for AT/ERROR responses with findInBuf function instead
*    of strstr function so that incoming carbage won't confuse it
*
* Modified March 2006 by Tuukka Karvonen <tkarvone@iki.fi>
*  - Added -r option which makes the mux driver to restart itself in case
*    the modem stops responding. This should make the driver more fault
*    tolerant. 
*  - Some code restructuring that was required by the automatic restarting
*  - fixed open_pty function to grant right for Unix98 scheme pseudo
*    terminals even though symlinks are not in use
*
* Modified March 2006 by Alex Wolfson <aleksv@aplusmurals.com>
*   Added AT command scripting. This is done by executing an arbitrary command
*   for example -m "command chat FILENAME"
*
* New Usage:
* gsmMuxd [options] <pty1> <pty2> ...
*
* To see the options, type:
* ./gsmMuxd -h
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef _GNU_SOURCE
// To get ptsname grandpt and unlockpt definitions from stdlib.h
#define _GNU_SOURCE
#endif


#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "gsm_module.h"
#include "gsm0710.h"
#include "utils.h"


static int baudrates[] = { 
    0, 9600, 19200, 38400, 57600, 115200, 230400, 460800 };

static speed_t baud_bits[] = {
    0, B9600, B19200, B38400, B57600, B115200, B230400, B460800 };

static void PrintHex (register unsigned char * out, const unsigned char * in, int count)
{
	register unsigned char next_ch;
	static char hex[] = "0123456789ABCDEF";

	while (count-- > 0)
	{
		next_ch = *in++;
		*out++ = hex[(next_ch >> 4) & 0x0F];
		*out++ = hex[next_ch & 0x0F];        ++out;
	}
}

static void PrintChar (register unsigned char * out, const unsigned char * in, int count)
{
	register unsigned char next_ch;

	while (count-- > 0)
	{
		next_ch = *in++;
		if (next_ch < 0x20 || next_ch > 0x7e)
			*out++ = '.';
			else
			{
				*out++ = next_ch;
				if (next_ch == '%')   /* GSMMUX_PRINT/syslogd has a bug !! */
					*out++ = '%';
			}
	}

	*out = '\0';
}

static void PrintBuffer (const char *name, const unsigned char *buf, int count)
{
	unsigned char line[44*4]; 

	if (name != NULL)
		GSMMUX_PRINT("%s,n=%d\n", name, count);

	while (count > 6*4)
	{
		memset (line, 32, 44*4);
		PrintHex (line, buf, 6*4);
		PrintChar (&line[6 * 3*4], buf, 6*4);
		GSMMUX_PRINT("%s\n", line);
		count -= 6*4;
		buf += 6*4;
	}

	if (count > 0)
	{
		memset (line, 32, 44*4);
		PrintHex (line, buf, count); 
		PrintChar (&line[6 * 3*4], buf, count);
		GSMMUX_PRINT( "%s\n", line);
	}
}

/** Writes a frame to a logical channel. C/R bit is set to 1.
* Doesn't support FCS counting for UI frames.
*
* PARAMS:
* channel - channel number (0 = control)

* input   - the data to be written
* count   - the length of the data
* type    - the type of the frame (with possible P/F-bit)
*
* RETURNS:
* number of characters written
*/
int generic_write_frame(GsmModule* module, int channel, const char *input, int count, unsigned char type)
{
	
	// flag, EA=1 C channel, frame type, length 1-2
	unsigned char 	prefix[5] = { F_FLAG, EA | CR, 0, 0, 0 };
	unsigned char 	postfix[2] = { 0xFF, F_FLAG };
	int 			prefix_length = 4, c;
	char*			write_buf = NULL;

	// EA=1, Command, let's add address
	prefix[1] = prefix[1] | ((63 & (unsigned char) channel) << 2);
	// let's set control field
	prefix[2] = type;

	if (count > 127)
	{
		prefix_length = 5;
		prefix[3] = ((127 & count) << 1);
		prefix[4] = (32640 & count) >> 7;
	}
	else
	{
		prefix[3] = 1 | (count << 1);
	}
	// CRC checksum
	postfix[0] = make_fcs(prefix + 1, prefix_length - 1);
	
	write_buf = malloc(prefix_length + count + 2);
	memcpy (write_buf, prefix, prefix_length);
	memcpy (write_buf + prefix_length, input, count);
	memcpy (write_buf + prefix_length + count, postfix, 2);
	//write_file("/data/data/gsmmux_serial_write.bin", write_buf, prefix_length + count + 2);
#ifdef ENABLE_LOG
	PrintBuffer("mux write hex: ", write_buf, prefix_length + count + 2);
#endif

	if ((c = write (module->serial_fd, write_buf, prefix_length + count + 2)) != (prefix_length + count + 2))
	{
		GSMMUX_DEBUG ("%s: write_buf=%p, len=%d, failure\n", __func__, write_buf, prefix_length + count + 2); 
	}
	free(write_buf);

	return count;
}

// Returns 1 if found, 0 otherwise. needle must be null-terminated.
// strstr might not work because WebBox sends garbage before the first OK
int findInBuf(const unsigned char* buf, int len, char* needle) 
{
	int i;
	int needleMatchedPos=0;
  
	if (needle[0] == '\0') 
	{
		return 1;
	}

  	for (i=0;i<len;i++) 
  	{
		if (needle[needleMatchedPos] == buf[i]) 
		{
      		needleMatchedPos++;
      		if (needle[needleMatchedPos] == '\0') 
	  		{
				// Entire needle was found
				return 1; 
      		}      
    	} 
		else 
		{
      		needleMatchedPos=0;
    	}
	}
  return 0;
}

/* Sends an AT-command to a given serial port and waits
* for reply.
*
* PARAMS:
* fd  - file descriptor
* cmd - command
* to  - how many microseconds to wait for response (this is done 100 times)
* RETURNS:
* 1 on success (OK-response), 0 otherwise
*/
int at_command(int fd, char *cmd, int to)
{
	fd_set rfds;
	struct timeval timeout;
	unsigned char buf[1024];
	int sel, len, i;
	int returnCode = 0;
	int wrote = 0;

	if(fd <= 0) return 0;

	wrote = write(fd, cmd, strlen(cmd));
	for (i = 0; i < 100; i++)
	{

		FD_ZERO(&rfds);
		FD_SET(fd, &rfds);

		timeout.tv_sec = 0;
		timeout.tv_usec = to;

		if ((sel = select(fd + 1, &rfds, NULL, NULL, &timeout)) > 0)
		{

			if (FD_ISSET(fd, &rfds))
			{
				memset(buf, 0, sizeof(buf));
				len = read(fd, buf, sizeof(buf));

				//if (strstr(buf, "\r\nOK\r\n") != NULL)
				if (findInBuf(buf, len, "OK"))
				{
					returnCode = 1;
					break;
				}
				if (findInBuf(buf, len, "ERROR"))
				{
					break;
				}
			}

		}

	}

	return returnCode;
}

int open_pty(int idx, const char* dev_symlink_prefix) 
{
	struct termios 	options;
	int 			fd = open(USSP_DEV_NAME, O_RDWR | O_NONBLOCK);
	int				link_name_len = strlen(dev_symlink_prefix)  + 5;
	char 			*symLinkName = calloc(1, link_name_len);

	snprintf(symLinkName, link_name_len, "%s%d", dev_symlink_prefix, idx);
	
	if (fd != -1) 
	{
		if (symLinkName) 
		{
			char* ptsSlaveName = ptsname(fd);	  
			// Create symbolic device name, e.g. /dev/mux0
			unlink(symLinkName);
			if (symlink(ptsSlaveName, symLinkName) != 0) 
			{
				GSMMUX_DEBUG ("Can't create symbolic link %s -> %s. %s (%d).\n", symLinkName, ptsSlaveName, strerror(errno), errno);
			}
		}

		// get the parameters
		tcgetattr(fd, &options);
		// set raw input
		options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

//		options.c_iflag = IGNBRK;
		options.c_iflag  = IGNBRK | IGNPAR;
		options.c_iflag &= ~(INLCR | ICRNL | IGNCR);
			
		// set raw output
		options.c_oflag &= ~OPOST;
		options.c_oflag &= ~OLCUC;
		options.c_oflag &= ~ONLRET;
		options.c_oflag &= ~ONOCR;
		options.c_oflag &= ~OCRNL;
		tcsetattr(fd, TCSANOW, &options);
 
		// Otherwise programs cannot access the pseudo terminals
		grantpt(fd);
		unlockpt(fd);
	}
	chmod(symLinkName, 0777);
	free(symLinkName);

	return fd;
}


/**
 * Determine baud rate index for CMUX command
 */
int indexOfBaud(int baudrate) {
    int i;

    for (i = 0; i < sizeof(baudrates) / sizeof(baudrates[0]); ++i) {
        if (baudrates[i] == baudrate)
            return i;
    }
    return 0;
}

/** 
 * Set serial port options. Then switch baudrate to zero for a while
 * and then back up. This is needed to get some modems 
 * (such as Siemens MC35i) to wake up.
 */
void setAdvancedOptions(int fd, speed_t baud) 
{
    struct termios options;
    struct termios options_cpy;

    fcntl(fd, F_SETFL, 0);
    
    // get the parameters
    tcgetattr(fd, &options);
    
    // Do like minicom: set 0 in speed options
    cfsetispeed(&options, 0);
    cfsetospeed(&options, 0);
    
    options.c_iflag = IGNBRK;
    
    // Enable the receiver and set local mode and 8N1
    options.c_cflag = (CLOCAL | CREAD | CS8 | HUPCL);
	
    // enable hardware flow control (CNEW_RTCCTS)
#if USE_HARDWARE
    options.c_cflag |= CRTSCTS;
#endif
    
    // Set speed
    options.c_cflag |= baud;
    
    /*
      options.c_cflag &= ~PARENB;
      options.c_cflag &= ~CSTOPB;
      options.c_cflag &= ~CSIZE; // Could this be wrong!?!?!?
    */
    
    // set raw input
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_iflag &= ~(INLCR | ICRNL | IGNCR);
    
    // set raw output
    options.c_oflag &= ~OPOST;
    options.c_oflag &= ~OLCUC;
    options.c_oflag &= ~ONLRET;
    options.c_oflag &= ~ONOCR;
    options.c_oflag &= ~OCRNL;
    
    // Set the new options for the port...
    options_cpy = options;
    tcsetattr(fd, TCSANOW, &options);
    options = options_cpy;
    
    // Do like minicom: set speed to 0 and back
    options.c_cflag &= ~baud;
    tcsetattr(fd, TCSANOW, &options);
    options = options_cpy;
    
    options.c_cflag |= baud;
    tcsetattr(fd, TCSANOW, &options);
}

/* Opens serial port, set's it to 57600bps 8N1 RTS/CTS mode.
*
* PARAMS:
* dev - device name
* RETURNS :
* file descriptor or -1 on error
*/
int open_serialport(char *dev, int baudrate)
{
	int fd;

	fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY);
	if (fd != -1)
	{
		int index = indexOfBaud(baudrate);
		if (index > 0) {
			// Switch the baud rate to zero and back up to wake up 
			// the modem
			setAdvancedOptions(fd, baud_bits[index]);
		}
	}

	return fd;
}

// Prints information on a frame
void print_frame(GSM0710_Frame * frame)
{
	//GSMMUX_DEBUG( "is in %s\n" , __FUNCTION__);
	//GSMMUX_DEBUG("Received ");
	char buffer[10000] = {0};

    switch((frame->control & ~PF))
	{
		case SABM:
			GSMMUX_DEBUG("SABM ");
			break;
		case UIH:
			GSMMUX_DEBUG("UIH ");
			break;
		case UA:
			GSMMUX_DEBUG("UA ");
			break;
		case DM:
			GSMMUX_DEBUG("DM ");
			break;
		case DISC:
			GSMMUX_DEBUG("DISC ");
			break;
		case UI:
			GSMMUX_DEBUG("UI ");
			break;
		default:
			GSMMUX_DEBUG("unkown (control=%d) ", frame->control);
			break;
	}
	GSMMUX_DEBUG(" frame for channel %d.\n", frame->channel);

	if (frame->data_length > 0)
	{
		memcpy(buffer, frame->data, frame->data_length);
		GSMMUX_DEBUG("received: frame->data = %s, size = %d\n",buffer, frame->data_length);
	}

}

/* Handles commands received from the control channel.
*/
void handle_command(GsmModule* module, GSM0710_Frame * frame)
{
#if 1
	unsigned char type, signals;
	int length = 0, i, type_length, channel, supported = 1;
	unsigned char *response;
	// struct ussp_operation op;


	if (frame->data_length > 0)
	{
		type = frame->data[0];  // only a byte long types are handled now
		// skip extra bytes
		for (i = 0; (frame->data_length > i && (frame->data[i] & EA) == 0); i++);
		i++;
		type_length = i;
		if ((type & CR) == CR)
		{
			// command not ack

			// extract frame length
			while (frame->data_length > i)
			{
				length = (length * 128) + ((frame->data[i] & 254) >> 1);
				if ((frame->data[i] & 1) == 1)
					break;
				i++;
			}
			i++;

			switch((type & ~CR))
			{
				case C_CLD:
					GSMMUX_DEBUG("The mobile station requested mux-mode termination.\n");
					/*TODO: resett*/
					//
					break;
				case C_TEST:
	#ifdef DEBUG
					GSMMUX_DEBUG("frame->data = %s  / frame->data_length = %d\n",frame->data + i, frame->data_length - i);
					//fwrite(frame->data + i, sizeof(char), frame->data_length - i, stdout);
	#endif
					break;
				case C_MSC:
					if (i + 1 < frame->data_length)
					{
						channel = ((frame->data[i] & 252) >> 2);
						i++;
						signals = (frame->data[i]);
						// op.op = USSP_MSC;
						// op.arg = USSP_RTS;
						// op.len = 0;

						GSMMUX_DEBUG("Modem status command on channel %d.\n", channel);
						if ((signals & S_FC) == S_FC)
						{
							GSMMUX_DEBUG("No frames allowed.\n");
						}
						else
						{
							// op.arg |= USSP_CTS;
							GSMMUX_DEBUG("Frames allowed.\n");
						}
						if ((signals & S_RTC) == S_RTC)
						{
							// op.arg |= USSP_DSR;
							GSMMUX_DEBUG("RTC\n");
						}
						if ((signals & S_IC) == S_IC)
						{
							// op.arg |= USSP_RI;
							GSMMUX_DEBUG("Ring\n");
						}
						if ((signals & S_DV) == S_DV)
						{
							// op.arg |= USSP_DCD;
							GSMMUX_DEBUG("DV\n");
						}
						// if (channel > 0)
						//     write(ussp_fd[(channel - 1)], &op, sizeof(op));
					}
					else
					{
						GSMMUX_DEBUG("ERROR: Modem status command, but no info. i: %d, len: %d, data-len: %d\n", i, length,
						frame->data_length);
					}
					break;
				default:
					GSMMUX_DEBUG("Unknown command (%d) from the control channel.\n", type);
					response = malloc(sizeof(char) * (2 + type_length));
					response[0] = C_NSC;
					// supposes that type length is less than 128
					response[1] = EA & ((127 & type_length) << 1);
					i = 2;
					while (type_length--)
					{
						response[i] = frame->data[(i - 2)];
						i++;
					}
					MODULE_WRITE_FRAME(module, 0, response, i, UIH);
					free(response);
					supported = 0;
					break;
			}

			if (supported)
			{
				// acknowledge the command
				frame->data[0] = frame->data[0] & ~CR;
				MODULE_WRITE_FRAME(module, 0, frame->data, frame->data_length, UIH);
			}
		}
		else
		{
			// received ack for a command
			if (COMMAND_IS(C_NSC, type))
			{
				GSMMUX_DEBUG("The mobile station didn't support the command sent.\n");
			}
			else
			{
				GSMMUX_DEBUG("Command acknowledged by the mobile station.\n");
			}
		}
	}
#endif
}

/* Extracts and handles frames from the receiver buffer.
*
* PARAMS:
* buf - the receiver buffer
*/
int generic_extract_frames(GsmModule* module, GSM0710_Buffer * buf)
{
	// version test for Siemens terminals to enable version 2 functions
	int framesExtracted = 0;

	GSM0710_Frame *frame;

	while ((frame = gsm0710_buffer_get_frame(buf)))
	{
		++framesExtracted;
		if ((FRAME_IS(UI, frame) || FRAME_IS(UIH, frame)))
		{
			if (frame->channel > 0)
			{
				module->write_ussp_data (module, frame->channel, frame->data, frame->data_length);
#ifdef ENABLE_LOG
				print_frame(frame);
#endif
			}
			else
			{
				handle_command(module, frame);
			}

		}
		else
		{
			// not an information frame
#ifdef ENABLE_LOG
			print_frame(frame);
#endif
			switch((frame->control & ~PF))
			{
				case UA:
					GSMMUX_DEBUG("is FRAME_IS(UA, frame)\n");
					break;
				case DM:
					GSMMUX_DEBUG("Logical channel %d dm.\n", frame->channel);
					break;
				case DISC:
					GSMMUX_DEBUG("Received DISC even though channel %d was already closed.\n", frame->channel);
					MODULE_WRITE_FRAME(module, frame->channel, NULL, 0, DM | PF);
					break;
				case SABM:
					MODULE_WRITE_FRAME(module, frame->channel, NULL, 0, UA | PF);
					break;
			}
		}

		destroy_frame(frame);
	}
	
	return framesExtracted;
}

void close_serial_port (int fd)
{
	close (fd);

	return;
}

void close_pty (int fd, int idx, const char* symlink_prefix) 
{
	int link_name_len = strlen(symlink_prefix) + 5;
	char *symlinkName = calloc(1, link_name_len);

	snprintf(symlinkName, link_name_len, "%s%d", symlink_prefix, idx);
	
	close(fd);
	unlink(symlinkName);
	free(symlinkName);

	return;
}

